﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Media3D;
using System.Windows;
using System.Windows.Media;
using System.IO;
using System.Windows.Media.Imaging;
using System.Windows.Controls;

namespace Zcu.PanelComposer.ViewModels.Core
{
    /// <summary>
    /// Make 3D model from polygon.
    /// </summary>
    public class Polygon3DModel
    {
        #region Properties

        /// <summary>
        /// Gets the model.
        /// </summary>
        public Model3D Model { get; private set; }

        /// <summary>
        /// Gets the polygon collection.
        /// </summary>
        public IList<Point> PolygonCollection { get; private set; }

        /// <summary>
        /// Gets the floor triangles.
        /// </summary>
        public IList<IList<Point>> FloorTriangles { get; private set; }

        /// <summary>
        /// Gets the circumference.
        /// </summary>
        public double Circumference { get; private set; }

        /// <summary>
        /// Gets the height.
        /// </summary>
        public int Height { get; private set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="Polygon3DModel"/> class.
        /// </summary>
        /// <param name="polygonCollection">The polygon collection.</param>
        public Polygon3DModel(IList<Point> polygonCollection)
        {
            // pretocime polygon proti smeru hod. rucicek
            PolygonCollection = ClippingEar.MakeClockOtherwise(polygonCollection);
            // PolygonCollection = polygonCollection;

            // spocitame obvod polygonu
            for (int i = 0; i < PolygonCollection.Count; i++)
            {
                Point first = PolygonCollection[i];
                // pokud jsme na konci, tak bereme v potaz prvni prvek
                Point second = PolygonCollection[i == (PolygonCollection.Count-1) ? 0 : i+1];
                // nascitavani delky sten
                Circumference += PointDistance(first, second);
            }

            // vygenerujeme trojuhelniky
            FloorTriangles = ClippingEar.Triangulate(PolygonCollection);
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Creates the model.
        /// </summary>
        /// <param name="height">The height.</param>
        /// <returns></returns>
        public Model3D CreateModel(int height, Canvas canvas)
        {
            // vyska 3D modelu
            Height = height;

            // vracena sestava 3D prvku
            Model3DGroup group = new Model3DGroup();

            /// TODO vypichnout jinam! pouze pro test
            // string fullPath = Path.GetFullPath(path);
            // string fullPath = Path.GetFullPath(@"d:\canvas.png");
            // string fullPath = Path.GetFullPath(@"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg");

            //if (!File.Exists(fullPath))
            //{
            //    return null;
            //}

            //BitmapImage image = new BitmapImage();
            //image.BeginInit();
            //image.UriSource = new Uri(fullPath);
            //image.EndInit();

            // vektor smerujici vzhuru (z+)
            Vector3D up = new Vector3D(0, 0, 1);

            #region Strecha

            // geometrie strechy
            var roofMesh = new MeshGeometry3D();

            // barva strechy
            var roofBrush = Brushes.LightGray;

            // material strechy
            var roofMaterial = new DiffuseMaterial(roofBrush);

            int counter = 0;
            foreach (var triangles in FloorTriangles)
            {
                foreach (var triangle in triangles)
                {
                    roofMesh.Positions.Add(new Point3D(triangle.X, triangle.Y, height));
                    roofMesh.TriangleIndices.Add(counter);
                    roofMesh.Normals.Add(up);
                    counter++;
                }
            }
            var roofModel = new GeometryModel3D(roofMesh, roofMaterial);
            // pridame 3D model strechy do groupy
            group.Children.Add(roofModel);

            #endregion

            #region Obvod

            // geometrie obvodoveho zdiva
            var sideMesh = new MeshGeometry3D();

            //var brush = Brushes.Red;
            var sideBrush = new VisualBrush(canvas); // new ImageBrush(image);

            // POZOR! Nutne, jinak to neroztrha tapetu na dilci steny!
            sideBrush.ViewportUnits = BrushMappingMode.Absolute;
            var sideMaterial = new DiffuseMaterial(sideBrush);

            // naplnit body
            foreach (Point p in PolygonCollection)
            {
                // podlazi
                sideMesh.Positions.Add(new Point3D(p.X, p.Y, 0));
            }
            // a jeste jednou ten prvni
            var point = PolygonCollection[0];
            sideMesh.Positions.Add(new Point3D(point.X, point.Y, 0));
            foreach (Point p in PolygonCollection)
            {
                // strecha
                sideMesh.Positions.Add(new Point3D(p.X, p.Y, height));
            }
            // a jeste jednou prvni na strese
            sideMesh.Positions.Add(new Point3D(point.X, point.Y, height));

            // vzdalenost v obvodu
            double sideLength = 0.0;

            // j-ty prvek (hlidani konce kolekce)
            int j = 0;

            // seznam bodu pro stresni body 
            IList<Point> roofTextCoords = new List<Point>();

            // na zaver "ztrojuhelnikovat"
            for (int i = 0; i < PolygonCollection.Count; i++)
            {
                // C --- D
                // -     -
                // A --- B

                j = (i + 1); // % PolygonCollection.Count;

                // spocitame delku steny
                Point first = PolygonCollection[i];

                // pokud jsme na konci, tak bereme v potaz prvni prvek
                Point second = PolygonCollection[i == (PolygonCollection.Count-1) ? 0 : i+1];
                
                // pomer delky steny ku obvodu
                double side = sideLength / Circumference;

                // nejprve trojuhelnik ABC
                sideMesh.TriangleIndices.Add(i); 
                sideMesh.TriangleIndices.Add(j);
                // strecha je az za vsemi body podlazi
                sideMesh.TriangleIndices.Add(i + PolygonCollection.Count + 1);

                // ABC - textury
                sideMesh.TextureCoordinates.Add(new Point(side, 1));
                roofTextCoords.Add(new Point(side, 0));

                // ted trojuhelnik CBD
                sideMesh.TriangleIndices.Add(i + PolygonCollection.Count + 1);
                sideMesh.TriangleIndices.Add(j);
                sideMesh.TriangleIndices.Add(j + PolygonCollection.Count + 1);

                // dopocitat normaly
                // mesh.Normals.Add(normal);

                // pricteme delku steny
                sideLength += PointDistance(first, second);
            }

            sideMesh.TextureCoordinates.Add(new Point(1, 1));
            roofTextCoords.Add(new Point(1, 0));

            // pridame koordinaty stresnich bodu
            foreach (var item in roofTextCoords)
            {
                sideMesh.TextureCoordinates.Add(item);
            }

            var sideGeometry = new GeometryModel3D(sideMesh, sideMaterial);

            // pridame 3D model sten do groupy
            group.Children.Add(sideGeometry);

            #endregion

            // vratime 3D groupu
            return group;
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Points the distance.
        /// </summary>
        /// <param name="start">The start.</param>
        /// <param name="end">The end.</param>
        /// <returns></returns>
        private double PointDistance(Point start, Point end)
        {
            return Math.Sqrt(Math.Pow(end.X - start.X, 2) + Math.Pow(end.Y - start.Y, 2));
        }

        #endregion
    }
}
